// Package dao
// Created by GoLand
// @User: lenora
// @Date: 2024/7/3
// @Time: 17:46

package dao

import (
	"context"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"time"
)

var (
	ErrRecordNotFound = gorm.ErrRecordNotFound
)

type InteractiveDAO interface {
	IncrReadCnt(ctx context.Context, biz string, bizId uint64) error
	UpsertLike(ctx context.Context, biz string, id, userId uint64) error
	RemoveLike(ctx context.Context, biz string, id, userId uint64) error
	InsertCollect(ctx context.Context, biz string, id, cid, userId uint64) error
	Get(ctx context.Context, biz string, id uint64) (Interactive, error)
	GetLikeInfo(ctx context.Context, biz string, id, userId uint64) (UserLike, error)
	GetCollectInfo(ctx context.Context, biz string, id, userId uint64) (UserCollect, error)
	GetByIds(ctx context.Context, biz string, ids []uint64) ([]Interactive, error)
}

func NewGORMInteractiveDAO(db *gorm.DB) InteractiveDAO {
	return &GORMInteractiveDAO{db: db}
}

type GORMInteractiveDAO struct {
	db *gorm.DB
}

func (dao *GORMInteractiveDAO) GetByIds(ctx context.Context, biz string, ids []uint64) ([]Interactive, error) {
	var list []Interactive
	err := dao.db.WithContext(ctx).
		Where("biz = ? and biz_id in ?", biz, ids).
		Find(&list).
		Error
	return list, err
}

func (dao *GORMInteractiveDAO) Get(ctx context.Context, biz string, id uint64) (Interactive, error) {
	var res Interactive
	err := dao.db.WithContext(ctx).
		Where("biz_id = ? and biz = ?", id, biz).
		First(&res).
		Error
	return res, err
}

func (dao *GORMInteractiveDAO) GetLikeInfo(ctx context.Context, biz string, id, userId uint64) (UserLike, error) {
	var res UserLike
	err := dao.db.WithContext(ctx).
		Where("user_id = ? and biz_id = ? and biz = ? and status = 1", userId, id, biz).
		First(&res).Error
	return res, err
}

func (dao *GORMInteractiveDAO) GetCollectInfo(ctx context.Context, biz string, id, userId uint64) (UserCollect, error) {
	var res UserCollect
	err := dao.db.WithContext(ctx).
		Where("user_id = ? and biz_id = ? and biz = ?", userId, id, biz).
		First(&res).Error
	return res, err
}

func (dao *GORMInteractiveDAO) InsertCollect(ctx context.Context, biz string, id, cid, userId uint64) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Create(&UserCollect{
			UserId: userId,
			BizId:  id,
			Biz:    biz,
			Cid:    cid,
			BaseTime: BaseTime{
				Ctime: now,
				Utime: now,
			},
		}).Error
		if err != nil {
			return err
		}
		return tx.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"collect_cnt": gorm.Expr("`collect_cnt` + 1"),
				"utime":       now,
			}),
		}).Create(&Interactive{
			BizId:      id,
			Biz:        biz,
			CollectCnt: 1,
			BaseTime:   BaseTime{Ctime: now, Utime: now},
		}).Error
	})
}

func (dao *GORMInteractiveDAO) UpsertLike(ctx context.Context, biz string, id, userId uint64) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		//upsert用户点赞记录
		err := tx.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"status": 1,
				"utime":  now,
			}),
		}).Create(&UserLike{
			UserId: userId,
			BizId:  id,
			Biz:    biz,
			Status: 1,
			BaseTime: BaseTime{
				Ctime: now,
				Utime: now,
			},
		}).Error
		if err != nil {
			return err
		}
		//更新文章点藏评数
		return tx.Clauses(clause.OnConflict{
			DoUpdates: clause.Assignments(map[string]interface{}{
				"like_cnt": gorm.Expr("`like_cnt` + 1"),
				"utime":    now,
			}),
		}).Create(&Interactive{
			BizId:    id,
			Biz:      biz,
			LikeCnt:  1,
			BaseTime: BaseTime{Ctime: now, Utime: now},
		}).Error
	})
}

func (dao *GORMInteractiveDAO) RemoveLike(ctx context.Context, biz string, id, userId uint64) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Model(&UserLike{}).
			Where("user_id = ? and biz_id = ? and biz = ?", userId, id, biz).
			Updates(map[string]any{
				"status": 0,
				"utime":  now,
			}).Error
		if err != nil {
			return err
		}
		return tx.Model(&Interactive{}).
			Where("biz_id = ? and biz = ?", id, biz).
			Updates(map[string]any{
				"like_cnt": gorm.Expr("`like_cnt` - 1"),
				"utime":    now,
			}).Error
	})
}

func (dao *GORMInteractiveDAO) IncrReadCnt(ctx context.Context, biz string, bizId uint64) error {
	now := time.Now().UnixMilli()
	return dao.db.WithContext(ctx).Clauses(clause.OnConflict{
		DoUpdates: clause.Assignments(map[string]interface{}{
			"read_cnt": gorm.Expr("`read_cnt` + 1"),
			"utime":    now,
		}),
	}).Create(&Interactive{
		BizId:   bizId,
		Biz:     biz,
		ReadCnt: 1,
		BaseTime: BaseTime{
			Ctime: now,
			Utime: now,
		},
	}).Error
}

type UserLike struct {
	Id uint64 `gorm:"primaryKey,autoIncrement"`
	//索引--<userId,bizId,biz>
	UserId   uint64 `gorm:"uniqueIndex:user_biz_type_id"`
	BizId    uint64 `gorm:"uniqueIndex:user_biz_type_id"`
	Biz      string `gorm:"type:varchar(128);uniqueIndex:user_biz_type_id"`
	Status   uint8
	BaseTime `gorm:"inline"`
}

type UserCollect struct {
	Id uint64 `gorm:"primaryKey,autoIncrement"`
	//索引--<userId,bizId,biz>
	UserId uint64 `gorm:"uniqueIndex:user_biz_type_id"`
	BizId  uint64 `gorm:"uniqueIndex:user_biz_type_id"`
	Biz    string `gorm:"type:varchar(128);uniqueIndex:user_biz_type_id"`

	Cid      uint64 `gorm:"index"` //收藏夹id
	BaseTime `gorm:"inline"`
}

type Interactive struct {
	Id uint64 `gorm:"primaryKey,autoIncrement"`
	//索引--<bizId,biz>
	BizId      uint64 `gorm:"uniqueIndex:biz_type_id"`
	Biz        string `gorm:"type:varchar(128);uniqueIndex:biz_type_id"`
	ReadCnt    int64
	LikeCnt    int64
	CollectCnt int64
	BaseTime   `gorm:"inline"`
}

type BaseTime struct {
	Ctime int64
	Utime int64
}
